package com.beney.ds.trees.b;

// B+ Tree
public class BTreePrinceton<K extends Comparable<K>, V> {
    // M-1 children at most
    private static final int M = 4;

    private Node root;
    private int height;
    private int n; // 有效数据的个数

    private static final class Node {
        private int m;  // current number of children(entries)
        private Entry[] children = new Entry[M]; // the array of links to children

        private Node(int m) {
            this.m = m;
        }
    }

    // component of a Node
    // internal nodes: only use key and next, guide searching
    // external nodes: only use key and value, store data
    private static class Entry {
        private Comparable key;
        private Object val;
        private Node next;

        public Entry(Comparable key, Object val, Node next) {
            this.key = key;
            this.val = val;
            this.next = next;
        }
    }

    public BTreePrinceton() {
        this.root = new Node(0); // root initially store nothing
    }

    public int size() {
        return n;
    }

    public int height() {
        return height;
    }

    public V get(K k) {
        if (k == null)
            return null;
        return search(root, k, this.height);
    }

    private V search(Node x, K k, int h) {
        /*
         * a node contains at most M-1 keys and M entries to children
         *  node key  *   k1  k2  k3    km-1
         *             \   \   \   \      \
         * children     0   1   2   3 ...  M-1
         */
        final Entry[] children = x.children;
        // external node: stores data
        if (h == 0) {
            for (int i = 0; i < x.m; ++i)
                if (eq(k, children[i].key))
                    return (V) children[i].val;
        }
        // internal node: index
        else {
            for (int i = 0; i < x.m; ++i)
                if (i+1 == x.m || less(k, children[i+1].key))
                    return search(children[i].next, k, h - 1);
        }
        return null;
    }

    public boolean put(K k, V v) {
        if (k == null)
            return false;
        Node rightUncle = insert(root, k, v, height);
        n++;
        if (rightUncle != null) { // root has been splited
            Node newRoot = new Node(2); // node has 2 entries to children
            newRoot.children[0] = new Entry(root.children[0].key, null, root);
            newRoot.children[1] = new Entry(rightUncle.children[0].key, null, rightUncle);
            root = newRoot;
            height++;
        }
        return true;
    }

    /**
     * @return 结点分裂之后的左半部分新节点，null则没有分裂
     */
    private Node insert(Node x, K k, V v, int ht) {
        int i;
        Entry newEntry = new Entry(k, v, null);
        final Entry[] children = x.children;
        // external node: stores data
        if (ht == 0) {
            for (i = 0; i < x.m; ++i)
                if (less(k, children[i].key))
                    break;
        }
        // internal node: index
        else {
            for (i = 0; i < x.m; ++i)
                if (i + 1 == x.m || less(k, children[i+1].key)) {
                    Node u = insert(children[i++].next, k, v, ht - 1);
                    if (u == null) return null;
                    newEntry.key = u.children[0].key;
                    newEntry.val = null; // serve as a guide
                    newEntry.next = u;
                    break;
                }
        }
        for (int j = x.m; j > i; --j) // move backward
            children[j] = children[j-1];
        children[i] = newEntry;
        x.m++;
        if (x.m < M) return null;
        else return split(x);
    }

    /**
     * split node x in half
     *
     * @return right-part of the node
     */
    private Node split(Node x) {
        Node t = new Node(M / 2);
        x.m = M - M / 2;
        for (int i = 0; i < M/2; ++i)
            t.children[i] = x.children[M/2 + i];
        return t;
    }

    private boolean less(Comparable a, Comparable b) {
        return a.compareTo(b) < 0;
    }

    private boolean eq(Comparable a, Comparable b) {
        return a.compareTo(b) == 0;
    }

    /**
     * Returns a string representation of this B-tree (for debugging).
     *
     * @return a string representation of this B-tree.
     */
    public String toString() {
        return toString(root, height, "") + "\n";
    }

    private String toString(Node h, int ht, String indent) {
        StringBuilder s = new StringBuilder();
        Entry[] children = h.children;

        if (ht == 0) {
            for (int j = 0; j < h.m; j++) {
                s.append(indent + children[j].key + " " + children[j].val + "\n");
            }
        }
        else {
            for (int j = 0; j < h.m; j++) {
                if (j > 0) s.append(indent + "(" + children[j].key + ")\n");
                s.append(toString(children[j].next, ht-1, indent + "     "));
            }
        }
        return s.toString();
    }

    public static void main(String[] args) {
        BTreePrinceton<String, String> st = new BTreePrinceton<>();

        st.put("www.cs.princeton.edu", "128.112.136.12");
        st.put("www.cs.princeton.edu", "128.112.136.11");
        st.put("www.princeton.edu",    "128.112.128.15");
        st.put("www.yale.edu",         "130.132.143.21");
        st.put("www.simpsons.com",     "209.052.165.60");
        st.put("www.apple.com",        "17.112.152.32");
        st.put("www.amazon.com",       "207.171.182.16");
        st.put("www.ebay.com",         "66.135.192.87");
        st.put("www.cnn.com",          "64.236.16.20");
        st.put("www.google.com",       "216.239.41.99");
        st.put("www.nytimes.com",      "199.239.136.200");
        st.put("www.microsoft.com",    "207.126.99.140");
        st.put("www.dell.com",         "143.166.224.230");
        st.put("www.slashdot.org",     "66.35.250.151");
        st.put("www.espn.com",         "199.181.135.201");
        st.put("www.weather.com",      "63.111.66.11");
        st.put("www.yahoo.com",        "216.109.118.65");

        System.out.println("cs.princeton.edu:  " + st.get("www.cs.princeton.edu"));
        System.out.println("hardvardsucks.com: " + st.get("www.harvardsucks.com"));
        System.out.println("simpsons.com:      " + st.get("www.simpsons.com"));
        System.out.println("apple.com:         " + st.get("www.apple.com"));
        System.out.println("ebay.com:          " + st.get("www.ebay.com"));
        System.out.println("dell.com:          " + st.get("www.dell.com"));
        System.out.println();

        System.out.println("size:    " + st.size());
        System.out.println("height:  " + st.height());
        System.out.println(st);
        System.out.println();
    }
}

